package me.keiwu.user.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import me.keiwu.pub.data.ConfigType;
import me.keiwu.pub.data.ResponseV2;
import me.keiwu.pub.data.config.AppConfigBean;
import me.keiwu.pub.data.service.MiscKey;
import me.keiwu.pub.data.service.OtherUserInfoBean;
import me.keiwu.pub.data.service.UserInfoBean;
import me.keiwu.pub.data.wx.WXUserInfoData;
import me.keiwu.pub.util.DateUtil;
import me.keiwu.pub.util.EntityUtil;
import me.keiwu.pub.util.CacheUtil;
import me.keiwu.user.dao.CombAvatarDao;
import me.keiwu.user.dao.ItemDao;
import me.keiwu.user.dao.CombAvatarSettingDao;
import me.keiwu.user.dao.UserGroupDao;
import me.keiwu.user.dao.UserItemDao;
import me.keiwu.user.dao.UserStepDao;
import me.keiwu.user.dao.UserTotalStepDao;
import me.keiwu.user.domain.CombAvatar;
import me.keiwu.user.domain.Item;
import me.keiwu.user.domain.User;
import me.keiwu.user.domain.UserForm;
import me.keiwu.user.dao.UserDao;
import me.keiwu.user.dao.UserFormDao;
import me.keiwu.user.domain.CombAvatarSetting;
import me.keiwu.user.domain.UserGroup;
import me.keiwu.user.domain.UserItem;
import me.keiwu.user.domain.UserStep;
import me.keiwu.user.domain.UserTotalStep;
import me.keiwu.user.rpc.LogLoginRpc;
import me.keiwu.user.service.ItemService;
import me.keiwu.user.service.UserMiscService;
import me.keiwu.user.service.UserService;
import me.keiwu.pub.data.Token;
import me.keiwu.pub.data.LockKeyEnum;
import me.keiwu.pub.data.Response;
import me.keiwu.pub.data.wx.WXSessionData;
import me.keiwu.pub.util.TokenUtil;
import me.keiwu.pub.util.redis.RedisLock;
import me.keiwu.user.util.ExceptionUtil;
import me.keiwu.user.util.enums.ItemType;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.Predicate;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;

/**
 * Created by kei on 06/11/2017.
 */
@Transactional
@Service
public class UserServiceImpl implements UserService {

    private Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

	@Autowired
	private UserDao userDao;
	@Autowired
	private UserService userService;
	@Autowired
	private UserStepDao userStepDao;
	@Autowired
	private ItemDao itemDao;
	@Autowired
	private CombAvatarDao combAvatarDao;
	@Autowired
	private UserItemDao userItemDao;
	@Autowired
	private CombAvatarSettingDao combAvatarSettingDao;
	@Autowired
	private LogLoginRpc logLoginRpc;
	@Autowired
	private UserTotalStepDao userTotalStepDao;
	@Autowired
	private ItemService itemService;
	@Autowired
	private UserMiscService userMiscService;
	@Autowired
	private TokenUtil tokenUtil;
	@Autowired
	private RedisLock redisLock;
	@Autowired
	private CacheUtil cacheUtil;
	@Autowired
	private ExceptionUtil exceptionUtil;
	@Autowired
	private UserFormDao userFormDao;
	@Autowired
    private UserGroupDao userGroupDao;

    @Override
    public Response login(WXUserInfoData userInfo, WXSessionData session, String channel) {
        User dbUser = userDao.findByOpenId(userInfo.getOpenId());
        if (dbUser == null) dbUser = new User();
        EntityUtil.copyNonNullProperties(userInfo, dbUser);


        boolean isRegister = false;
        if (dbUser.getId() != null) {
            userService.save(dbUser);
            logger.info("update user! user id = {}", dbUser.getId());
        } else {
            // 只有注册的时候更新channel
            isRegister = true;
            dbUser.setChannel(channel);
            // 初始化赠送星币
            AppConfigBean appConfigBean = cacheUtil.getConfig(ConfigType.APP_CONFIG, AppConfigBean.class);
            int initGold = appConfigBean.getInitGold();
            dbUser.setGold(initGold);
            userService.save(dbUser);
            logger.info("insert user! user id = {}", dbUser.getId());
        }

        logLoginRpc.logLogin(dbUser.getId(), dbUser.getNickName(), dbUser.getChannel(), DateUtil.getUnixTime());

        // 刷新或者生成token
        String tokenStr = tokenUtil.getAccessToken(dbUser.getId());
        Token token;
        if (StringUtils.isBlank(tokenStr)) {
            token = tokenUtil.createAccessToken(dbUser.getId());
        } else {
            token = tokenUtil.updateAccessToken(dbUser.getId(), tokenStr);
        }
        // 缓存session
        cacheUtil.setWXSession(dbUser.getId(), session);

        JSONObject ret = new JSONObject();
        ret.put("isNewUser", isRegister);
        ret.put("userId", dbUser.getId());
        ret.put("openId", dbUser.getOpenId());
        ret.put("accessToken", token);
        return Response.ok(ret);
    }


    @Override
    public User save(User user) {
        User u = userDao.save(user);
        this.cleanUserCache(u.getId());
        return u;
    }

    @Override
    public void save(Iterable<User> users) {
        userDao.save(users);
        users.forEach(u -> this.cleanUserCache(u.getId()));
    }


    @Override
    public User getUserById(Long userId) {
        return userDao.findOne(userId);
    }

    @Override
    public List<User> getUsersByIdList(List<Long> idList) {
        return userDao.getUsersByIdList(idList);
    }


    @Override
    public UserInfoBean getUserInfo(Long userId) {
        return this.getUserInfo(userId, false);
    }

    private UserInfoBean getUserInfo(Long userId, boolean cache) {
        User user = this.getUserById(userId);
        UserInfoBean userInfo = this.transferUserInfo(user);
        // 缓存info
        if (cache) cacheUtil.setLoginUserInfo(userId, userInfo);
        return userInfo;
    }

    private UserInfoBean transferUserInfo(User user) {
        if (user == null) return null;
        UserInfoBean userInfo = new UserInfoBean();
        // 实时更新头像
        String avatar = this.getUserAvatar(user.getId());
        // 更新等级
        if (user.getLevel() == null || user.getLevel() <= 0) {
            userInfo.setLevel(itemService.calcUserLevel(user.getId()));
        } else {
            userInfo.setLevel(user.getLevel());
        }
//        logger.info("user userId={}, avatar={}", userId, avatar);
        userInfo.setAvatarUrl(avatar == null ? user.getAvatarUrl() : avatar);
        userInfo.setWxAvatarUrl(user.getWxAvatarUrl());
        userInfo.setCity(user.getCity());
        userInfo.setLbsCityName(user.getLbsCityName());
        userInfo.setCountry(user.getCountry());
        userInfo.setGender(user.getGender());
        userInfo.setGold(user.getGold());
        userInfo.setId(user.getId());
        userInfo.setNickName(user.getNickName());
        userInfo.setProvince(user.getProvince());
        userInfo.setTelephone(user.getTelephone());
        userInfo.setOpenId(user.getOpenId());
        userInfo.setUnionId(user.getUnionId());
        userInfo.setCreatedTime(user.getCreatedTime());
        userInfo.setLastModifiedTime(user.getLastModifiedTime());

        UserStep step = this.getDailyUserStep(user.getId());
        if (step != null) {
            userInfo.setStep(step.getStep());
            // 总步数，从注册日开始算
            UserTotalStep totalStep = this.getUserTotalStep(user.getId());
            logger.info("user total step userId={}, totalStep={}", user.getId(), totalStep);
            userInfo.setTotalStep(totalStep.getTotalStep() + step.getStep());
            // 30天总步数
            userInfo.setTotalStep30(totalStep.getTotalStep30());
        }

        return userInfo;
    }


    @Override
    public UserInfoBean getUserInfoCache(Long userId) {
        UserInfoBean bean = cacheUtil.getLoginUserInfo(userId);
        if (bean == null) return this.getUserInfo(userId, true);
        return bean;
    }

    @Override
    public UserInfoBean getUserInfoByOpenId(String openId) {
        User user = userDao.findByOpenId(openId);
        return this.transferUserInfo(user);
    }

    @Override
    public String getUserAvatar(Long userId) {
        CombAvatarSetting setting = combAvatarSettingDao.findOne(userId);

        Integer suitId = null;
        Integer singleId = null;
        if (setting == null) {
            // 还要考虑用户已经兑换过套装
            boolean hasSuit = false;
            List<UserItem> userItemList = userItemDao.findAllByUserId(userId);
            for (UserItem item : userItemList) {
                if (item.getItemType() == ItemType.SUIT.getType()) {
                    suitId = item.getItemId();
                    hasSuit = true;
                    break;
                }
            }
            if (!hasSuit) return null;
        } else {
            suitId = setting.getSuitId();
            singleId = setting.getSingleId();
        }

        Item suit = itemDao.findOne(suitId);
        if (suit == null || suit.isDel() || !suit.isShelve()) return null;

        if (singleId != null) {
            Item single = itemDao.findOne(singleId);
            if (single == null || single.isDel() || !single.isShelve()) {
                // 单件下架了，直接返回套装图片
                return suit.getImgUrl();
            }
        } else {
            // 没有单件
            return suit.getImgUrl();
        }
        CombAvatar combAvatar = combAvatarDao.findBySuitIdAndSingleId(suitId, singleId);
        // 如果没有组合形象就显示套装形象
        return combAvatar != null ? combAvatar.getImgUrl() : suit.getImgUrl();
    }

    @Override
    public OtherUserInfoBean getOtherUserInfo(Long userId) {
        User user = this.getUserById(userId);
        if (user == null) return null;
        OtherUserInfoBean userInfo = new OtherUserInfoBean();
        // 实时更新头像
        String avatar = this.getUserAvatar(userId);
        userInfo.setAvatarUrl(avatar == null ? user.getAvatarUrl() : avatar);
        userInfo.setCity(user.getCity());
        userInfo.setLbsCityName(user.getLbsCityName());
        userInfo.setCountry(user.getCountry());
        userInfo.setGender(user.getGender());
        userInfo.setNickName(user.getNickName());
        userInfo.setProvince(user.getProvince());

        UserStep step = this.getDailyUserStep(userId);
        if (step != null) userInfo.setStep(step.getStep());
        return userInfo;
    }


    @SuppressWarnings("Just for admin system")
    @Override
    public Page<User> getDailyLoginUser(String nickName, int page, int size) {
        Specification<User> spec = (root, query, cb) -> {
            long todayStart = DateUtil.getDayStart(new Date()).getTime();
            Predicate p1 = cb.not(cb.lessThan(root.get("lastModifiedTime"), todayStart));
            Predicate p2 = cb.notEqual(root.get("channel"), "robot");
            Predicate p3 = cb.isNull(root.get("channel"));
            if (StringUtils.isNotBlank(nickName)) {
                return cb.and(p1, cb.or(p2, p3), cb.like(root.get("nickName"), "%" + nickName + "%"));
            }
            return cb.and(p1, cb.or(p2, p3));
        };
        Sort sort = new Sort(Sort.Direction.DESC, "lastModifiedTime");
        Pageable pageable = new PageRequest(page, size, sort);
        return userDao.findAll(spec, pageable);
    }

    @SuppressWarnings("Just for admin system")
    @Override
    public Page<User> getAllLoginUser(String nickName, int page, int size) {
        Specification<User> spec = (root, query, cb) -> {
            Predicate p1 = cb.notEqual(root.get("channel"), "robot");
            Predicate p2 = cb.isNull(root.get("channel"));
            if (StringUtils.isNotBlank(nickName)) {
                return cb.and(cb.or(p1, p2), cb.like(root.get("nickName"), "%" + nickName + "%"));
            }
            return cb.or(p1, p2);
        };
        Sort sort = new Sort(Sort.Direction.DESC, "createdTime");
        Pageable pageable = new PageRequest(page, size, sort);
        return userDao.findAll(spec, pageable);
    }


    @SuppressWarnings("Just for admin system")
    @Override
    public Page<User> findWithoutUsers(List<Long> withoutList, String nickName, Integer page, Integer size) {
        Specification<User> spec = (root, query, cb) -> {
            Predicate p = cb.notEqual(root.get("channel"), "robot");
            p = cb.or(p, cb.isNull(root.get("channel")));
            if (withoutList != null && withoutList.size() > 0) {
                p = cb.and(p, cb.not(root.get("id").in(withoutList)));
            }
            if (StringUtils.isNotBlank(nickName)) {
                p = cb.and(p, cb.like(root.get("nickName"), "%" + nickName + "%"));
            }
            return p;
        };
        Sort sort = new Sort(Sort.Direction.DESC, "lastModifiedTime");
        Pageable pageable = new PageRequest(page, size, sort);
        return userDao.findAll(spec, pageable);
    }


    @Override
    public boolean changeGold(long userId, int gold) {
        User user = this.getUserById(userId);
        return this.changeGold(user, gold);
    }

    @Override
    public boolean changeGold(User user, int gold) {
        int newGold = user.getGold() + gold;
        logger.info("try to changeGold userId={}, gold={}, newGold={}", user.getId(), gold, newGold);
        if (newGold < 0) return false;
        return this.updateGold(user, newGold);
    }


    @Override
    public boolean updateGold(long userId, int newGold) {
        User user = this.getUserById(userId);
        return this.updateGold(user, newGold);
    }


    @Override
    public boolean updateGold(User user, int newGold) {
        logger.info("updateGold userId={}, newGold={}", user.getId(), newGold);
        String lockKey = LockKeyEnum.CHANGE_GOLD.getLockKey(user.getId());
        String requestId = UUID.randomUUID().toString();
        try {
            if (!redisLock.tryLock(lockKey, requestId)) {
                return false;
            }
            user.setGold(newGold);
            this.save(user);
            // 刷新缓存
            this.cleanUserCache(user.getId());
            return true;
        } catch (Exception e) {
            logger.error("decrGold exception: ", e);
            exceptionUtil.logError(this.getClass(), "updateGold", "decrGold gold fail!");
            return false;
        } finally {
            redisLock.release(lockKey, requestId);
        }
    }


    @Override
    public UserStep getDailyUserStep(Long userId) {
        return userStepDao.findInSameDay(userId, DateUtil.getDayStrInt(new Date()));
    }

    @Override
    public List<UserStep> getUserSteps(Long userId, Integer fromDays, Integer toDays) {
        return userStepDao.findFromTo(userId, fromDays, toDays);
    }

    @Override
    public UserTotalStep getUserTotalStep(Long userId) {
        UserTotalStep total = userTotalStepDao.getUserTotalStep(userId);
        return total == null ? new UserTotalStep() : total;
    }



    @Override
    public void cleanUserCache(Long userId) {
        cacheUtil.cleanUserInfo(userId);
    }


    @Override
    public ResponseV2 getRank(Long start, Long end) {
        Set<String> ids = cacheUtil.getRank(start, end);
        JSONArray array = new JSONArray();
        ids.forEach(id -> {
            Long userId = Long.valueOf(id);
            JSONObject json = new JSONObject();
            json.put("userInfo", this.getUserInfo(userId));
            json.put("rank", cacheUtil.getUserRank(userId));
            json.put("score", cacheUtil.getRankScore(userId));
            array.add(json);
        });
        return ResponseV2.ok(array);
    }

    @Override
    public ResponseV2 getUserRank(Long userId) {
        JSONObject json = new JSONObject();
        json.put("userInfo", this.getUserInfo(userId));
        json.put("rank", cacheUtil.getUserRank(userId));
        json.put("score", cacheUtil.getRankScore(userId));
        return ResponseV2.ok(json);
    }

    @Override
    public ResponseV2 joinGroupRank(String openGId, Long userId, String groupName) {
        String key = cacheUtil.getGroupRankKey(openGId);
        Double score = cacheUtil.getRankScore(userId);
        cacheUtil.addRankMember(key, userId + "", score);

        UserGroup ug = userGroupDao.findByUserIdAndOpenGid(userId, openGId);
        if (ug == null) {
            ug = new UserGroup();
            ug.setUserId(userId);
            ug.setOpenGid(openGId);
        }
        ug.setGroupName(groupName);
        userGroupDao.save(ug);
        // 返回群排行榜
        return ResponseV2.ok(ug);
    }

    @Override
    public ResponseV2 getGroupRank(String openGId, Long start, Long end) {
        String key = cacheUtil.getGroupRankKey(openGId);
        Set<String> ids = cacheUtil.getRank(key, start, end);
        JSONArray array = new JSONArray();
        ids.forEach(id -> {
            Long user = Long.valueOf(id);
            JSONObject json = new JSONObject();
            json.put("rank", cacheUtil.getUserRank(key, user));
            json.put("score", cacheUtil.getRankScore(key, user));
            json.put("userInfo", this.getUserInfo(user));
            array.add(json);
        });
        return ResponseV2.ok(array);
    }

    @Override
    public ResponseV2 getUserGroupRank(String openGId, Long userId) {
        String key = cacheUtil.getGroupRankKey(openGId);
        JSONObject json = new JSONObject();
        json.put("userInfo", this.getUserInfo(userId));
        json.put("rank", cacheUtil.getUserRank(key, userId));
        json.put("score", cacheUtil.getRankScore(key, userId));
        return ResponseV2.ok(json);
    }

    @Override
    public ResponseV2 getUserGroupList(Long userId, Integer page, Integer size) {
        Specification<UserGroup> spec = (root, query, cb) -> {
            return cb.equal(root.get("userId"), userId);
        };
        Sort sort = new Sort(Sort.Direction.DESC, "lastModifiedTime");
        Pageable pageable = new PageRequest(page, size, sort);
        Page<UserGroup> list = userGroupDao.findAll(spec, pageable);
        return ResponseV2.ok(list);
    }

    @Override
    public ResponseV2 shareGetGold(Long userId) {
        // 保存分享misc
        AppConfigBean config = cacheUtil.getConfig(ConfigType.APP_CONFIG, AppConfigBean.class);
        if (config == null) return ResponseV2.fail("缺少应用配置");

        if (userMiscService.get(userId, MiscKey.SHARE_GET_GOLD) != null) return ResponseV2.ok();
        userMiscService.save(userId, MiscKey.SHARE_GET_GOLD, "");
        int get = config.getShareGetGold();
        if (userService.changeGold(userId, Math.abs(get))) {
            return ResponseV2.ok(get);
        }
        exceptionUtil.logError(this.getClass(), "shareGetGold", "change gold fail! userId=" + userId);
        return ResponseV2.ok();
    }

	//
	// 保存UserForm
	@Override
	public ResponseV2 saveUserForm(UserForm userForm) {
		userFormDao.save(userForm);
		return ResponseV2.ok("保存成功");
	}

	// 通过用户ID查询UserForm
	@Override
	public UserForm getOldestUserFormById(Long userId) {
		UserForm uf = userFormDao.findByUserId(userId);
        return uf;
	}

	// 删除过期
	@Override
	public boolean deleteExpiredForm() {
        userFormDao.deleteExpiredForm();
        return true;
	}

	@Override
    public boolean updateUsedForm(String formId){
         userFormDao.updateUsedFormId(formId);
         return true;
    }

    @Override
    public boolean deleteUsedForm(Long id){
        userFormDao.delete(id);
        return true;
    }

    @Override
    public Page<User> getInactiveUser(Integer page, Integer size){
//        Date date = new Date();
//        long startDateMillisSeconds = DateUtil.addDays(date, -5).getTime();
//        long endDateMillisSeconds = DateUtil.addDays(date, -7).getTime();
//        exceptionUtil.logError(this.getClass(),"getInactiveUser","start:"+startDateMillisSeconds+" end:"+endDateMillisSeconds);
//        Specification<User> spec = (root, query, cb) -> {
//            Predicate p1 = cb.notEqual(root.get("channel"), "robot");
//            Predicate p2 = cb.isNull(root.get("channel"));
//            Predicate p3 = cb.between(root.get("lastModifiedTime"),startDateMillisSeconds,endDateMillisSeconds);
//            Predicate p = cb.and(cb.or(p1,p2),p3);
//
//            return p;
//        };
//        Sort sort = new Sort(Sort.Direction.DESC, "lastModifiedTime");
//        Pageable pageable = new PageRequest(page, size, sort);
//        return userDao.findAll(spec, pageable);
        Pageable pageable = new PageRequest(page, size);
        return userDao.getInactiveUsers(pageable);
    }

    @Override
    public ResponseV2 getRankForAdmin(Long start, Long end) {
        Set<String> ids = cacheUtil.getRank(start, end);
        JSONArray array = new JSONArray();
        ids.forEach(id -> {
            Long userId = Long.valueOf(id);
            JSONObject json = new JSONObject();
            User user = this.getUserById(userId);
            json.put("userId", userId);
            json.put("nickName", user.getNickName());
            json.put("openId", user.getOpenId());
            json.put("rank", cacheUtil.getUserRank(userId));
            json.put("score", cacheUtil.getRankScore(userId));
            array.add(json);
        });
        return ResponseV2.ok(array);
    }

    public static void main(String[] args) {
		System.err.println(RandomStringUtils.randomAlphanumeric(16));
	}

}
